package com.gzc.just.play.last.war.sceneserver.skill;

import com.gzc.just.play.last.war.sceneserver.gameobject.unit.GameUnit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

/**
 * SkillManager类的单元测试
 * 
 * @author AI Assistant
 * @version 1.0.0
 * @since 2025-11-25
 */
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@DisplayName("技能管理器测试")
class SkillManagerTest {

    @Mock
    private GameUnit mockOwner;
    
    @Mock
    private SkillListener mockListener;
    
    private SkillManager skillManager;
    private Skill testSkill1;
    private Skill testSkill2;
    private Skill testSkill3;
    
    @BeforeEach
    void setUp() {
        skillManager = new SkillManager(mockOwner);
        
        // 创建测试技能
        testSkill1 = new Skill(1, "火球术", SkillType.SINGLE_TARGET, 1);
        testSkill1.setManaCost(50);
        testSkill1.setCooldownTime(5000);
        testSkill1.setDamageMultiplier(1.5f);
        testSkill1.setBaseDamage(100);
        // 设置初始释放时间，确保不在冷却中
        testSkill1.setLastCastTime(0);
        
        testSkill2 = new Skill(2, "治疗术", SkillType.HEAL, 1);
        testSkill2.setManaCost(30);
        testSkill2.setCooldownTime(3000);
        testSkill2.setDamageMultiplier(0.0f);
        testSkill2.setBaseDamage(0);
        // 设置初始释放时间，确保不在冷却中
        testSkill2.setLastCastTime(0);
        
        testSkill3 = new Skill(3, "群体冰冻", SkillType.AOE, 1);
        testSkill3.setManaCost(100);
        testSkill3.setCooldownTime(10000);
        testSkill3.setDamageMultiplier(1.0f);
        testSkill3.setBaseDamage(80);
        
        // 设置单位的魔力值
        when(mockOwner.getCurrentMana()).thenReturn(200);
    }
    
    @Test
    @DisplayName("测试技能学习")
    void testSkillLearning() {
        // 学习新技能
        skillManager.learnSkill(testSkill1);
        
        // 验证技能已学习
        assertEquals(1, skillManager.getSkillCount());
        assertEquals(testSkill1, skillManager.getSkill(1));
        
        // 学习多个技能
        skillManager.learnSkill(testSkill2);
        skillManager.learnSkill(testSkill3);
        
        assertEquals(3, skillManager.getSkillCount());
        assertEquals(testSkill2, skillManager.getSkill(2));
        assertEquals(testSkill3, skillManager.getSkill(3));
        
        // 尝试重复学习同一技能
        skillManager.learnSkill(testSkill1);
        assertEquals(3, skillManager.getSkillCount()); // 数量不应增加
        
        // 尝试学习null技能
        skillManager.learnSkill(null);
        assertEquals(3, skillManager.getSkillCount()); // 数量不应增加
    }
    
    @Test
    @DisplayName("测试技能遗忘")
    void testSkillForgetting() {
        // 先学习技能
        skillManager.learnSkill(testSkill1);
        skillManager.learnSkill(testSkill2);
        skillManager.learnSkill(testSkill3);
        assertEquals(3, skillManager.getSkillCount());
        
        // 遗忘技能
        skillManager.forgetSkill(2);
        assertEquals(2, skillManager.getSkillCount());
        assertNull(skillManager.getSkill(2));
        assertEquals(testSkill1, skillManager.getSkill(1));
        assertEquals(testSkill3, skillManager.getSkill(3));
        
        // 尝试遗忘不存在的技能
        skillManager.forgetSkill(99);
        assertEquals(2, skillManager.getSkillCount()); // 数量不应变化
    }
    
    @Test
    @DisplayName("测试技能升级")
    void testSkillUpgrading() {
        // 学习技能
        skillManager.learnSkill(testSkill1);
        int initialLevel = testSkill1.getSkillLevel();
        int initialManaCost = testSkill1.getManaCost();
        
        // 升级技能
        skillManager.upgradeSkill(1, 2); // 升2级
        assertEquals(initialLevel + 2, testSkill1.getSkillLevel());
        assertTrue(testSkill1.getManaCost() > initialManaCost); // 魔力消耗应增加
        
        // 尝试升级不存在的技能
        skillManager.upgradeSkill(99, 1);
        assertEquals(initialLevel + 2, testSkill1.getSkillLevel()); // 技能1等级不变
    }
    
    @Test
    @DisplayName("测试技能释放条件检查")
    void testSkillCastConditionCheck() {
        // 学习技能
        skillManager.learnSkill(testSkill1);
        skillManager.learnSkill(testSkill2);
        
        long currentTime = System.currentTimeMillis();
        
        // 检查可释放的技能
        assertTrue(skillManager.canCastSkill(1)); // 火球术应该可释放
        
        // 魔力不足的情况
        when(mockOwner.getCurrentMana()).thenReturn(10); // 少于火球术消耗
        assertFalse(skillManager.canCastSkill(1));
        
        // 恢复魔力
        when(mockOwner.getCurrentMana()).thenReturn(200);
        assertTrue(skillManager.canCastSkill(1));
        
        // 不存在的技能
        assertFalse(skillManager.canCastSkill(99));
    }
    
    @Test
    @DisplayName("测试技能释放")
    void testSkillCasting() {
        // 学习技能
        skillManager.learnSkill(testSkill1);
        skillManager.learnSkill(testSkill2);
        
        // 使用一个固定的时间点
        long testTime = 1000000L;
        long initialCastTime = skillManager.getLastSkillCastTime();
        
        // 确保技能不在冷却中
        testSkill1.setLastCastTime(0);
        testSkill2.setLastCastTime(0);
        
        // 释放技能 - 先确保技能不在冷却中
        // 设置一个很早的时间戳，确保不在冷却中
        long pastTime = System.currentTimeMillis() - testSkill1.getCooldownTime() - 1000;
        testSkill1.setLastCastTime(pastTime);
        assertTrue(skillManager.castSkill(1));
        assertTrue(skillManager.getLastSkillCastTime() >= testTime);
        assertTrue(skillManager.getLastSkillCastTime() > initialCastTime);
        
        // 验证魔力消耗
        verify(mockOwner, times(1)).restoreMana(-50); // 火球术消耗50点魔力
        
        // 释放不可释放的技能
        // 创建一个魔力不足的技能管理器
        GameUnit lowManaOwner = mock(GameUnit.class);
        when(lowManaOwner.getCurrentMana()).thenReturn(10);
        SkillManager lowManaSkillManager = new SkillManager(lowManaOwner);
        lowManaSkillManager.learnSkill(testSkill1);
        assertFalse(lowManaSkillManager.castSkill(1)); // 魔力不足
    }
    
    @Test
    @DisplayName("测试冷却管理")
    void testCooldownManagement() {
        // 学习技能
        skillManager.learnSkill(testSkill1);
        
        long currentTime = System.currentTimeMillis();
        
        // 释放技能后应进入冷却
        skillManager.castSkill(1);
        assertTrue(skillManager.getSkillCooldown(1) > 0);
        
        // 重置冷却
        skillManager.resetSkillCooldown(1);
        assertEquals(0, skillManager.getSkillCooldown(1));
        
        // 获取所有冷却中的技能
        skillManager.castSkill(1); // 再次释放，进入冷却
        List<Skill> skillsInCooldown = skillManager.getSkillsInCooldown();
        assertEquals(1, skillsInCooldown.size());
        assertEquals(testSkill1, skillsInCooldown.get(0));
        
        // 重置所有冷却
        skillManager.resetAllCooldowns();
        assertEquals(0, skillManager.getSkillsInCooldown().size());
    }
    
    @Test
    @DisplayName("测试技能查询")
    void testSkillQueries() {
        // 学习不同类型的技能
        skillManager.learnSkill(testSkill1); // 单体伤害
        skillManager.learnSkill(testSkill2); // 治疗
        skillManager.learnSkill(testSkill3); // AOE
        
        // 获取所有技能
        Collection<Skill> allSkills = skillManager.getAllSkills();
        assertEquals(3, allSkills.size());
        assertTrue(allSkills.contains(testSkill1));
        assertTrue(allSkills.contains(testSkill2));
        assertTrue(allSkills.contains(testSkill3));
        
        // 按类型获取技能
        List<Skill> damageSkills = skillManager.getSkillsByType(SkillType.SINGLE_TARGET);
        assertEquals(1, damageSkills.size());
        assertEquals(testSkill1, damageSkills.get(0));
        
        List<Skill> healSkills = skillManager.getSkillsByType(SkillType.HEAL);
        assertEquals(1, healSkills.size());
        assertEquals(testSkill2, healSkills.get(0));
        
        List<Skill> aoeSkills = skillManager.getSkillsByType(SkillType.AOE);
        assertEquals(1, aoeSkills.size());
        assertEquals(testSkill3, aoeSkills.get(0));
        
        List<Skill> unknownSkills = skillManager.getSkillsByType(SkillType.UNKNOWN);
        assertEquals(0, unknownSkills.size());
        
        // 获取最后释放的技能
        testSkill1.setLastCastTime(0);
        testSkill2.setLastCastTime(0);
        assertTrue(skillManager.castSkill(1));
        assertEquals(testSkill1, skillManager.getLastCastedSkill());
        
        // 等待一小段时间，确保第二个技能的时间戳更新
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        assertTrue(skillManager.castSkill(2));
        assertEquals(testSkill2, skillManager.getLastCastedSkill());
    }
    
    @Test
    @DisplayName("测试技能管理")
    void testSkillManagement() {
        // 学习技能
        skillManager.learnSkill(testSkill1);
        skillManager.learnSkill(testSkill2);
        
        assertEquals(2, skillManager.getSkillCount());
        
        // 清除所有技能
        skillManager.clearAllSkills();
        assertEquals(0, skillManager.getSkillCount());
        assertNull(skillManager.getSkill(1));
        assertNull(skillManager.getSkill(2));
    }
    
    @Test
    @DisplayName("测试技能监听器")
    void testSkillListeners() {
        // 添加监听器
        skillManager.addListener(mockListener);
        
        // 学习技能
        skillManager.learnSkill(testSkill1);
        verify(mockListener, times(1)).onSkillLearned(testSkill1);
        
        // 升级技能
        skillManager.upgradeSkill(1, 1);
        verify(mockListener, times(1)).onSkillUpgraded(eq(testSkill1), eq(1));
        
        // 释放技能
        skillManager.castSkill(1);
        verify(mockListener, times(1)).onSkillCasted(testSkill1);
        
        // 遗忘技能
        skillManager.forgetSkill(1);
        verify(mockListener, times(1)).onSkillForgotten(testSkill1);
        
        // 移除监听器
        skillManager.removeListener(mockListener);
        
        // 再次学习技能，监听器不应被调用
        skillManager.learnSkill(testSkill2);
        verify(mockListener, never()).onSkillLearned(testSkill2);
    }
    
    @Test
    @DisplayName("测试技能冷却更新")
    void testCooldownUpdate() {
        // 学习技能
        skillManager.learnSkill(testSkill1);
        skillManager.learnSkill(testSkill2);
        
        long currentTime = System.currentTimeMillis();
        
        // 释放技能
        skillManager.castSkill(1);
        skillManager.castSkill(2);
        
        // 验证技能在冷却中
        assertTrue(skillManager.getSkillCooldown(1) > 0);
        assertTrue(skillManager.getSkillCooldown(2) > 0);
        
        // 模拟时间流逝
        long futureTime = currentTime + 6000; // 6秒后
        
        // 更新冷却
        skillManager.updateCooldowns(futureTime);
        
        // 验证冷却更新（基于实际实现，这里只是验证方法不抛异常）
        assertDoesNotThrow(() -> skillManager.updateCooldowns(futureTime));
    }
    
    @Test
    @DisplayName("测试技能摘要")
    void testSkillSummary() {
        // 学习技能
        skillManager.learnSkill(testSkill1);
        skillManager.learnSkill(testSkill2);
        
        // 获取技能摘要
        String summary = skillManager.getSkillsSummary();
        
        // 验证摘要包含关键信息
        assertTrue(summary.contains("技能列表"));
        assertTrue(summary.contains("火球术"));
        assertTrue(summary.contains("治疗术"));
        assertTrue(summary.contains("等级1"));
        assertTrue(summary.contains("冷却"));
        assertTrue(summary.contains("魔力"));
        
        // 清除技能后的摘要
        skillManager.clearAllSkills();
        summary = skillManager.getSkillsSummary();
        assertTrue(summary.contains("无技能"));
    }
    
    @Test
    @DisplayName("测试边界情况")
    void testEdgeCases() {
        // 测试空技能管理器
        assertEquals(0, skillManager.getSkillCount());
        assertNull(skillManager.getSkill(1));
        assertFalse(skillManager.canCastSkill(1));
        assertFalse(skillManager.castSkill(1));
        assertEquals(0, skillManager.getSkillCooldown(1));
        assertTrue(skillManager.getAllSkills().isEmpty());
        assertTrue(skillManager.getSkillsByType(SkillType.SINGLE_TARGET).isEmpty());
        assertTrue(skillManager.getSkillsInCooldown().isEmpty());
        assertNull(skillManager.getLastCastedSkill());
        
        // 测试添加null监听器
        skillManager.addListener(null); // 不应抛出异常
        
        // 测试移除null监听器
        skillManager.removeListener(null); // 不应抛出异常
        
        // 测试学习null技能
        skillManager.learnSkill(null); // 不应抛出异常
        
        // 测试对null技能操作
        skillManager.upgradeSkill(0, 1); // 不应抛出异常
        skillManager.forgetSkill(0); // 不应抛出异常
    }
    
    @Test
    @DisplayName("测试技能释放时间记录")
    void testSkillCastTimeRecording() {
        // 学习技能
        skillManager.learnSkill(testSkill1);
        skillManager.learnSkill(testSkill2);
        
        long currentTime = System.currentTimeMillis();
        
        // 初始状态
        assertEquals(0, skillManager.getLastSkillCastTime());
        
        // 确保技能不在冷却中
        testSkill1.setLastCastTime(0);
        testSkill2.setLastCastTime(0);
        
        // 释放技能1
        assertTrue(skillManager.castSkill(1));
        assertTrue(skillManager.getLastSkillCastTime() >= currentTime);
        
        // 短时间内再次释放技能2
        // 等待一小段时间，确保第二个技能的时间戳更新
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        assertTrue(skillManager.castSkill(2));
        
        // 验证最后释放的技能是技能2
        assertEquals(testSkill2, skillManager.getLastCastedSkill());
    }
    
    @Test
    @DisplayName("测试技能升级属性变化")
    void testSkillUpgradeAttributeChanges() {
        // 学习技能
        skillManager.learnSkill(testSkill1);
        
        int initialLevel = testSkill1.getSkillLevel();
        int initialBaseDamage = testSkill1.getBaseDamage();
        int initialManaCost = testSkill1.getManaCost();
        
        // 升级技能
        skillManager.upgradeSkill(1, 2);
        
        // 验证属性变化
        assertEquals(initialLevel + 2, testSkill1.getSkillLevel());
        assertTrue(testSkill1.getBaseDamage() > initialBaseDamage);
        assertTrue(testSkill1.getManaCost() > initialManaCost);
    }
}